package ws.cmpe451.group7.ourcity.webservice.place;

import java.io.IOException;
import java.io.PrintWriter;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.Hibernate;
import org.json.JSONException;
import org.json.simple.parser.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import ws.cmpe451.group7.ourcity.webservice.Callable;
import ws.cmpe451.group7.ourcity.webservice.ControllerWrapper;
import ws.cmpe451.group7.ourcity.webservice.RequestParameters;
import ws.cmpe451.group7.ourcity.webservice.WebserviceUtils;
import ws.cmpe451.group7.ourcity.webservice.exception.GeneralIllegalArgumentException;
import ws.cmpe451.group7.ourcity.webservice.place.exception.PlaceServletIllegalArgumentException;
import cmpe451.group7.ourcity.dao.CommentDAO;
import cmpe451.group7.ourcity.dao.MultimediaDAO;
import cmpe451.group7.ourcity.dao.PlaceDAO;
import cmpe451.group7.ourcity.dao.UserDAO;
import cmpe451.group7.ourcity.entity.Comment;
import cmpe451.group7.ourcity.entity.Multimedia;
import cmpe451.group7.ourcity.entity.MultimediaType;
import cmpe451.group7.ourcity.entity.Place;
import cmpe451.group7.ourcity.entity.Tag;
import cmpe451.group7.ourcity.entity.User;
import cmpe451.group7.ourcity.recommendation.FreebaseQuery;
import cmpe451.group7.ourcity.service.CommentService;
import cmpe451.group7.ourcity.service.UserService;
import cmpe451.group7.ourcity.util.Aes;
import cmpe451.group7.ourcity.util.GlobalVariables;
import cmpe451.group7.ourcity.util.JSONUtils;
import cmpe451.group7.ourcity.util.Utils;

/**
 * Controller for place related jobs
 */
@Controller
public class PlaceController {

	@Autowired
	private PlaceDAO placeDAO;

	@Autowired
	private UserDAO userDAO;

	@Autowired
	private CommentDAO commentDAO;

	@Autowired
	private MultimediaDAO multimediaDAO;

	@Autowired
	private UserService userService;

	@Autowired
	private CommentService commentService;

	/**
	 * Search the places in the database with the given arguments.
	 * 
	 * If 'id' is given only that place will be returned if it exists. If 'all'
	 * is given, all the places are returned. There are other parameters like
	 * placeName, etc.
	 * 
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	@Transactional
	@RequestMapping("/webservice/place/search")
	public void search(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/*
		 * This call returns a JSON object with the result set coming from DAO
		 * object
		 */
		response.setContentType("application/json");
		PrintWriter responseWriter = response.getWriter();
		Map<String, String[]> parameters = request.getParameterMap();
		Set<String> keys = parameters.keySet();

		try {
			/*
			 * If the request contains only all, return all places
			 */
			if (keys.size() == 0) {
				throw new PlaceServletIllegalArgumentException();
			} else if (keys.contains("all")) {
				// check the url for correctness
				if (keys.size() != 1)
					throw new PlaceServletIllegalArgumentException();

				// retrieve the places
				List<Place> places = placeDAO.findAll();

				// write the places to output stream as a JSON object
				JSONUtils.JSONListWriter(places, responseWriter);
			} else if (keys.contains("id")) {
				// check the url for correctness
				if (keys.size() != 1)
					throw new PlaceServletIllegalArgumentException();

				long placeId = Long.parseLong(parameters.get("id")[0]);
				Place place = placeDAO.findByID(placeId);

				// write the places to output stream as a JSON object
				JSONUtils.JSONEntityWriter(place, responseWriter);
			} else {

				String placeName = "";
				String country = "";
				String city = "";
				Date fromDate = null;
				Date toDate = null;
				String[] tags = null;

				// get arguments and check for request parameter correctness
				for (String arg : keys) {
					switch (arg) {
					case "placeName":
						placeName = parameters.get(arg)[0];
						break;
					case "country":
						country = parameters.get(arg)[0];
						break;
					case "city":
						city = parameters.get(arg)[0];
						break;
					case "tags":
						tags = parameters.get(arg);
						break;
					default:
						throw new PlaceServletIllegalArgumentException();
					}
				}

				LinkedList<Tag> selectedTags = new LinkedList<Tag>();
				if (tags.length != 0) {
					String tagMID = tags[0];
					if (StringUtils.isNotEmpty(tagMID)) {
						Tag t = FreebaseQuery.findTagByMID(tagMID);
						selectedTags.add(t);
					}
				}

				// retrieve the places that fit to the description
				// TODO: tags is empty
				List<Place> places = placeDAO.findByRestrictions(placeName,
						country, city, fromDate, toDate, selectedTags, 0, 0, 0,
						0);

				// write the places to the output stream
				JSONUtils.JSONListWriter(places, responseWriter);
			}
		} catch (JSONException e) {
			// an unhandled error is occurred here
			responseWriter
					.print("{\"success\":0, \"error\":\"Servlet error\"}");
		} catch (PlaceServletIllegalArgumentException e) {
			// request contains illegal parameter
			responseWriter
					.print("{\"success\":0, \"error\":\"Unsupported action\"}");
		} catch (NumberFormatException e) {
			//
			responseWriter
					.print("{\"success\":0, \"error\":\"Number format exception\"}");
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			responseWriter
					.print("{\"success\":0, \"error\":\"Freebase error\"}");
		}

		responseWriter.close();
	}

	/**
	 * Adds the given place to the liked place list of the user
	 * 
	 * Arguments: placeID, userID and user's password
	 * 
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@Transactional
	@RequestMapping("/webservice/place/likePlace")
	public void likePlace(HttpServletRequest request,
			HttpServletResponse response) throws IOException {

		Callable func = new Callable() {

			@Override
			public void call(HttpServletRequest request,
					HttpServletResponse response, PrintWriter responseWriter,
					Map<String, String[]> parameters)
					throws InvalidKeyException, NoSuchAlgorithmException,
					NoSuchPaddingException, IllegalBlockSizeException,
					BadPaddingException, InvalidKeySpecException,
					GeneralIllegalArgumentException {

				long placeID = Utils.parseLongFromMap(parameters, "placeID");
				long userID = Utils.parseLongFromMap(parameters, "userID");
				String password = parameters.get("password")[0];
				password = Aes.decryptPassword(password);

				Place place = placeDAO.findByID(placeID);

				// TODO use the user later !!!
				User user = userService.authenticate(userID, password);

				if (place == null || user == null) {
					throw new GeneralIllegalArgumentException(
							"Place or user does not exist");
				}

				Hibernate.initialize(user.getLikedPlace());

				if (!user.getLikedPlace().contains(place)) {
					place.setLikeCount(place.getLikeCount() + 1);
					user.getLikedPlace().add(place);

					placeDAO.update(place);
					userDAO.update(user);

					responseWriter.printf("{\"success\":1}");
				} else {
					responseWriter.printf("{\"success\":0}");
				}
			}

		};

		ControllerWrapper.wrapMethod(request, response, func,
				GlobalVariables.likePageRequiredParameters);
	}

	/**
	 * Return the comments given the place
	 * 
	 * Arguments: placeID
	 * 
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@Transactional
	@RequestMapping("/webservice/place/getComments")
	public void getComments(HttpServletRequest request,
			HttpServletResponse response) throws IOException {

		Callable func = new Callable() {

			@Override
			public void call(HttpServletRequest request,
					HttpServletResponse response, PrintWriter responseWriter,
					Map<String, String[]> parameters) throws JSONException {

				Long placeID = Utils.parseLongFromMap(parameters, "placeID");
				List<Comment> comments = commentDAO.findPlaceComments(placeID);

				for (Comment c : comments) {
					c.getUser().setBadges(null);
					c.getUser().setLikedPlace(null);
				}

				JSONUtils.JSONListWriter(comments, responseWriter);
			}

		};

		ControllerWrapper.wrapMethod(request, response, func,
				GlobalVariables.getPlaceCommentRequiredParameters);
	}

	/**
	 * Adds the comment to the given place
	 * 
	 * Arguments: placeID, userID, user password, comment
	 * 
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@Transactional
	@RequestMapping("/webservice/place/addComment")
	public void addComment(HttpServletRequest request,
			HttpServletResponse response) throws IOException {

		Callable func = new Callable() {

			@Override
			public void call(HttpServletRequest request,
					HttpServletResponse response, PrintWriter responseWriter,
					Map<String, String[]> parameters)
					throws InvalidKeyException, NoSuchAlgorithmException,
					NoSuchPaddingException, IllegalBlockSizeException,
					BadPaddingException, InvalidKeySpecException,
					GeneralIllegalArgumentException {

				long placeID = Utils.parseLongFromMap(parameters, "placeID");
				long userID = Utils.parseLongFromMap(parameters, "userID");
				String password = parameters.get("password")[0];
				password = Aes.decryptPassword(password);
				String commentText = parameters.get("comment")[0];

				Place place = placeDAO.findByID(placeID);
				User user = userService.authenticate(userID, password);

				if (place == null || user == null) {
					throw new GeneralIllegalArgumentException(
							"Place or user does not exist");
				}

				Comment comment = new Comment();
				comment.setComment(commentText);
				comment.setPlace(place);
				comment.setUser(user);
				comment.setDate(new Date());

				boolean commentAdded = commentDAO.save(comment);

				if (!commentAdded) {
					throw new GeneralIllegalArgumentException(
							"Comment cannot be added");
				}

				responseWriter.printf("{\"success\":1}");
			}

		};

		ControllerWrapper.wrapMethod(request, response, func,
				GlobalVariables.addCommentRequiredParameters);
	}

	/**
	 * Create a place
	 * 
	 * Arguments: placename, city, country, lat/lng
	 * 
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	@Transactional
	@RequestMapping("/webservice/place/create")
	public void createPlace(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		/*
		 * This call returns a JSON object with the result set coming from DAO
		 * object
		 */
		response.setContentType("application/json");
		PrintWriter responseWriter = response.getWriter();
		Map<String, String[]> parameters = request.getParameterMap();
		Set<String> keys = parameters.keySet();

		try {
			/*
			 * If the request contains only all, return all places
			 */
			if (keys.size() == 0) {
				throw new PlaceServletIllegalArgumentException();
			} else {
				String placeName = "";
				String country = "";
				String city = "";
				int likeCount = 0;
				double latitude = 0;
				double longitude = 0;
				Date creationDate = null;
				String[] tags = null;
				long userID = -1;
				String password = null;

				// get arguments and check for request parameter correctness
				for (String arg : keys) {
					switch (arg) {
					case "password":
						password = parameters.get(arg)[0];
						break;
					case "placeName":
						placeName = parameters.get(arg)[0];
						break;
					case "country":
						country = parameters.get(arg)[0];
						break;
					case "city":
						city = parameters.get(arg)[0];
						break;
					case "latitude":
						latitude = Double.parseDouble(parameters.get(arg)[0]);
						break;
					case "userID":
						userID = Long.parseLong(parameters.get(arg)[0]);
						break;
					case "longitude":
						longitude = Double.parseDouble(parameters.get(arg)[0]);
						break;
					case "tags":
						tags = parameters.get(arg);
						break;
					default:
						throw new PlaceServletIllegalArgumentException();
					}
				}

				if (creationDate == null) {
					creationDate = new Date();
				}

				if (userID <= 0 || password == null) {
					responseWriter
							.print("{\"success\":0, \"error\":\"No user info is given\"}");
					return;
				}

				try {
					password = Aes.decryptPassword(password);
				} catch (InvalidKeyException | NoSuchPaddingException
						| IllegalBlockSizeException | BadPaddingException e1) {
					responseWriter
							.print("{\"success\":0, \"error\":\"Illegal password encoding\"}");
					return;
				}

				User user = userService.authenticate(userID, password);
				if (user == null) {
					responseWriter
							.print("{\"success\":0, \"error\":\"User authentication error\"}");
					return;
				}

				LinkedList<Tag> tagList = new LinkedList<Tag>();

				if (tags.length != 0) {
					String tagMID = tags[0];
					if (StringUtils.isNotEmpty(tagMID)) {
						Tag tag = FreebaseQuery.findTagByMID(tagMID);
						tagList.add(tag);
					}
				}

				// TODO: tags & user is given empty
				Place newPlace = null;
				try {
					newPlace = placeDAO.insertNewPlace(placeName, country,
							city, likeCount, latitude, longitude, creationDate,
							tagList, user);
				} catch (Exception e) {
					responseWriter
							.print("{\"success\":0, \"error\":\"Error occured while adding the place\"}");
					return;
				}

				// write the places to the output stream
				JSONUtils.JSONEntityWriter(newPlace, responseWriter);
			}
		} catch (JSONException | NoSuchAlgorithmException
				| InvalidKeySpecException e) {
			// an unhandled error is occurred here
			responseWriter
					.print("{\"success\":0, \"error\":\"Servlet error\"}");
		} catch (PlaceServletIllegalArgumentException e) {
			// request contains illegal parameter
			responseWriter
					.print("{\"success\":0, \"error\":\"Unsupported action\"}");
		} catch (NumberFormatException e) {
			responseWriter
					.print("{\"success\":0, \"error\":\"Number format exception\"}");
		} catch (ParseException e) {
			responseWriter
					.print("{\"success\":0, \"error\":\"Error occured while parsing Freebase response\"}");
		}

		responseWriter.close();
	}

	/**
	 * Delete a place from the database (DEBUGGING PURPOSES ONLY)
	 * 
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@Transactional
	@RequestMapping("/webservice/place/delete")
	public void deletePlace(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		String id_string = request.getParameter("placeID");
		long id = Long.parseLong(id_string);
		Place p = placeDAO.findByID(id);
		placeDAO.delete(p);

		ServletOutputStream outputStream = response.getOutputStream();
		PrintWriter out = new PrintWriter(outputStream);

		out.printf("Place %d is deleted !\n", id);
		out.flush();
	}

	/**
	 * Upload an image to the place
	 * 
	 * Parameters: placeID, userID, fileExtension, user password
	 * 
	 * @param request
	 * @param response
	 * @param image
	 *            file uploaded by the android
	 * @throws IOException
	 */
	@Transactional
	@RequestMapping("/webservice/place/uploadImage")
	public void uploadImage(HttpServletRequest request,
			HttpServletResponse response,
			@RequestParam("image") final CommonsMultipartFile image)
			throws IOException {

		Callable func = new Callable() {

			@Override
			public void call(HttpServletRequest request,
					HttpServletResponse response, PrintWriter responseWriter,
					Map<String, String[]> parameters)
					throws IllegalStateException, IOException,
					ServletException, GeneralIllegalArgumentException,
					NoSuchAlgorithmException, InvalidKeySpecException,
					InvalidKeyException, NoSuchPaddingException,
					IllegalBlockSizeException, BadPaddingException {

				long placeID = Utils.parseLongFromMap(parameters, "placeID");
				long userID = Utils.parseLongFromMap(parameters, "userID");

				String mimeType = parameters.get("fileExtension")[0];
				String password = parameters.get("password")[0];
				password = Aes.decryptPassword(password);

				User user = userService.authenticate(userID, password);
				Place p = placeDAO.findByID(placeID);

				if (user == null || p == null) {
					throw new GeneralIllegalArgumentException(
							"user or place info is wrong");
				}

				if (image != null && !image.isEmpty()) {
					byte[] fileBuffer = image.getBytes();

					Multimedia m = new Multimedia();
					m.setOwner(user);
					m.setFile(fileBuffer);
					m.setMimeType(mimeType);
					m.setMultimediaType(MultimediaType.IMAGE);
					m.setMimeType(image.getContentType());

					multimediaDAO.save(m);

					p.addMultimedia(m);
					placeDAO.save(p);

					WebserviceUtils.printSuccessMessage(responseWriter);

				} else {
					throw new GeneralIllegalArgumentException("image is empty");
				}

			}
		};

		ControllerWrapper.wrapMethod(request, response, func,
				RequestParameters.placeImageUploadParameters);
	}

	@Transactional
	@RequestMapping("/webservice/place/searchComments")
	public void searchComments(HttpServletRequest request,
			HttpServletResponse response,
			@RequestParam("placeID") Long placeId,
			@RequestParam("tag") String tagMID) throws IOException {

		LinkedList<Tag> tagList = new LinkedList<Tag>();

		PrintWriter responseWriter = response.getWriter();

		if (StringUtils.isNotEmpty(tagMID)) {
			Tag tag = null;
			try {
				tag = FreebaseQuery.findTagByMID(tagMID);
			} catch (ParseException e) {
				responseWriter
						.print("{\"success\":0, \"error\":\"Error occured while parsing Freebase response\"}");
				return;
			}
			tagList.add(tag);
		}
		commentService.findByRestrictions(placeId, "", null, null, tagList);
	}
}